package com.gbei.center.oauth.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.gbei.center.enums.ExpireEnum;
import com.gbei.center.enums.GrantTypeEnum;
import com.gbei.center.model.OpenApplication;
import com.gbei.center.model.User;
import com.gbei.center.oauth.mapper.*;
import com.gbei.center.oauth.model.*;
import com.gbei.center.oauth.req.AgreeReq;
import com.gbei.center.oauth.req.LoginReq;
import com.gbei.center.oauth.req.RefreshTokenReq;
import com.gbei.center.oauth.req.TokenReq;
import com.gbei.center.oauth.service.AuthorizationService;
import com.gbei.center.oauth.service.RedisService;
import com.gbei.center.utils.DateUtils;
import com.gbei.center.utils.EncryptUtils;
import com.gbei.center.utils.IdUtil;
import com.gbei.center.utils.ali.oss.AliOssUtil;
import com.gbei.center.utils.message.ResponseInfo;
import com.gbei.center.utils.message.Result;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class AuthorizationServiceImpl implements AuthorizationService {
    private Logger log = LoggerFactory.getLogger(AuthorizationServiceImpl.class);

    @Autowired
    private RedisService redisService;
    @Autowired
    private AuthAccessTokenMapper authAccessTokenMapper;
    @Autowired
    private AuthRefreshTokenMapper authRefreshTokenMapper;
    @Autowired
    private AuthClientUserMapper authClientUserMapper;
    @Autowired
    private OpenApplicationMapper openApplicationMapper;
    @Autowired
    private PlatformUserMapper platformUserMapper;
    @Autowired
    private AliOssUtil aliOssUtil;


    /**
     * 授权登录校验
     * @param req
     * @return
     */
    @Override
    public Result<?> checkLogin(LoginReq req, HttpServletRequest request){

        User user = platformUserMapper.selectByUserId(req.getUserId());

        //登录验证通过
        if(user != null){
            //1. 查询是否存在授权信息
            OpenApplication openApp = openApplicationMapper.selectByClientId(req.getAppId());

            if(openApp == null){
                return ResponseInfo.FAILURE.toResult("appId无效");
            }

            if ("platform".equalsIgnoreCase(openApp.getAppType())){
                return ResponseInfo.FAILURE.toResult("该应用没有授权登录权限");
            }

            //判断客户端类型  返回相应的回调地址
            String redirectUrl = openApp.getNotifyUrl();

            /*switch(getClient(request)){
                case "pc" :
                    redirectUrl = openApp.getNotifyUrl();
                    break;
                case "ios" :
                    redirectUrl = openApp.getNotifyUrl();
                    break;
                case "android" :
                    redirectUrl = openApp.getAppPackage();
                    break;
                default :
                    redirectUrl = "weChat";
            }*/

            //应用图标
            String appIcon = openApp.getIcon();
            if (StringUtils.isNoneBlank(appIcon)){
                appIcon = aliOssUtil.domainUrl(appIcon);
            }

            //2. 查询用户给接入的客户端是否已经授权
            AuthClientUser clientUser = authClientUserMapper.selectByClientId(openApp.getAppId(), user.getUserId());
            if(clientUser != null){
                String code = createAuthorizationCode(req.getAppId(),null,user);
                Map<String,Object> map = new HashMap<>();
                map.put("code",code);
                map.put("redirectUrl",redirectUrl);
                log.info("@@@@@@@@@@@@ 获取授权码code的返回值：{}", JSONObject.toJSONString(map));
                return ResponseInfo.SUCCESS.messageToResult(map);
            }else{
                //3 保存本次请求所属的用户信息(保存时间为2分钟)
                String flag = IdUtil.getId();
                redisService.setWithExpire(flag + ":user", user, (ExpireEnum.AUTHORIZATION_FLAG.getTime()), ExpireEnum.AUTHORIZATION_FLAG.getTimeUnit());
                Map<String,Object> map = new HashMap<>();
                map.put("appId",req.getAppId());
                map.put("flag",flag);
                map.put("appName",openApp.getAppName());
                map.put("redirectUrl",redirectUrl);
                map.put("appIcon",appIcon);
                log.info("@@@@@@@@@@@@ 获取授权码code的返回值：{}", JSONObject.toJSONString(map));
                return ResponseInfo.INVALID_AUTHORIZATION.messageToResult(map);
            }

        }else{
            return ResponseInfo.FAILURE.toResult("授权登录失败");
        }
    }


    /**
     * 获取客户端类型
     * @param request
     * @return
     */
    public String getClient(HttpServletRequest request){
        String client;
        String userAgent = request.getHeader("user-agent").toLowerCase();
        if(userAgent.indexOf("micromessenger")!= -1){
            client = "weChat";
        }else if(userAgent.indexOf("android") != -1){
            client = "android";
        }else if(userAgent.indexOf("iphone") != -1 || userAgent.indexOf("ipad") != -1 || userAgent.indexOf("ipod") != -1){
            client = "ios";
        }else{
            client = "pc";
        }
        return client;
    }



    /**
     * 用户同意授权
     * @param req
     * @return
     */
    @Override
    public Result<?> agree(AgreeReq req){
        //根据用户授权确认码获取用户信息
        User user =  redisService.get(req.getFlag() + ":user");
        if (user == null){
            return ResponseInfo.FAILURE.toResult("授权确认码(flag)无效");
        }

        //1. 向数据库中保存授权信息
        boolean saveFlag = saveAuthClientUser(user.getUserId(), req.getAppId(), null);

        if(!saveFlag){
            return ResponseInfo.FAILURE.toResult("授权失败");
        }
        //授权码
        String code = createAuthorizationCode(req.getAppId(),null,user);
        Map<String,Object> map = new HashMap<>();
        map.put("code",code);
        return ResponseInfo.SUCCESS.messageToResult(map);
    }

    /**
     * 生成token
     * @param req
     * @return
     */
    @Override
    public Result<?> token(TokenReq req){
        Map<String,Object> result = new HashMap<>(8);

        //校验授权方式
        if(!GrantTypeEnum.AUTHORIZATION_CODE.getType().equals(req.getGrantType()) && !GrantTypeEnum.CLIENT_CREDENTIALS.getType().equals(req.getGrantType())){
            return ResponseInfo.UNSUPPORTED_GRANT_TYPE.messageToResult();
        }

        try {
            OpenApplication openApp = openApplicationMapper.selectByClientId(req.getAppId());
            //校验请求的客户端秘钥和已保存的秘钥是否匹配
            if(!(openApp != null && openApp.getAppSecret().equals(req.getAppSecret()))){
                return ResponseInfo.INVALID_CLIENT.messageToResult();
            }

            //校验授权方式  客户端接入
            if(GrantTypeEnum.CLIENT_CREDENTIALS.getType().equals(req.getGrantType()) ){

                //过期时间
                Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());

                //生成Access Token
                String accessTokenStr = createAccessToken(openApp, req.getGrantType(), null, expiresIn);
                //查询已经插入到数据库的Access Token
                AuthAccessToken authAccessToken = authAccessTokenMapper.selectByAccessToken(accessTokenStr);
                //生成Refresh Token
                String refreshTokenStr = createRefreshToken(authAccessToken);

                //返回数据
                result.put("token", authAccessToken.getAccessToken());
                result.put("refreshToken", refreshTokenStr);
                result.put("expiresIn", expiresIn);
                return ResponseInfo.SUCCESS.messageToResult(result);
            }

            //前面获取的Authorization Code
            if (!StringUtils.isNoneBlank(req.getCode())){
                return ResponseInfo.FAILURE.toResult("授权码code不能为空");
            }

            //从Redis获取对应的用户信息
            User user = redisService.get(req.getCode() + ":user");

            //如果能够通过Authorization Code获取到对应的用户信息，则说明该Authorization Code有效
            if(user == null){
                return ResponseInfo.FAILURE.toResult("授权码code无效");
            }
            //过期时间
            Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());

            //生成Access Token
            String accessTokenStr = createAccessToken(user, openApp, req.getGrantType(), null, expiresIn);
            //查询已经插入到数据库的Access Token
            AuthAccessToken authAccessToken = authAccessTokenMapper.selectByAccessToken(accessTokenStr);
            //生成Refresh Token
            String refreshTokenStr = createRefreshToken(user, authAccessToken);

            //返回数据
            result.put("token", authAccessToken.getAccessToken());
            result.put("refreshToken", refreshTokenStr);
            result.put("expiresIn", expiresIn);
            log.info("@@@@@@@@@@@@ 获取token的返回值：{}", JSONObject.toJSONString(result));
            return ResponseInfo.SUCCESS.messageToResult(result);
        }catch (Exception e){
            e.printStackTrace();
            return ResponseInfo.FAILURE.messageToResult();
        }
    }


    /**
     * 刷新token
     * @param req
     * @return
     */
    @Override
    public Result<?> refreshToken(RefreshTokenReq req){
        Map<String,Object> result = new HashMap<>(8);

        OpenApplication openApp = openApplicationMapper.selectByClientId(req.getAppId());

        //校验请求的客户端秘钥和已保存的秘钥是否匹配
        if(!(openApp != null && openApp.getAppSecret().equals(req.getAppSecret()))){
            return ResponseInfo.INVALID_CLIENT.messageToResult();
        }

        try {
            AuthRefreshToken authRefreshToken = authRefreshTokenMapper.selectByRefreshToken(req.getRefreshToken());

            if(authRefreshToken == null) {
                return ResponseInfo.FAILURE.toResult("refreshToken无效");
            }
            Long savedExpiresAt = authRefreshToken.getExpiresIn();
            //过期日期
            LocalDateTime expiresDateTime = DateUtils.ofEpochSecond(savedExpiresAt, null);
            //当前日期
            LocalDateTime nowDateTime = DateUtils.now();

            //如果Refresh Token已经失效，则需要重新生成
            if (expiresDateTime.isBefore(nowDateTime)) {
                return ResponseInfo.EXPIRED_TOKEN.messageToResult();
            } else {
                //获取存储的Access Token
                AuthAccessToken authAccessToken = authAccessTokenMapper.selectByPrimaryKey(authRefreshToken.getTokenId());

                //新的过期时间
                Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());

                String newAccessTokenStr;

                if ("client_credentials".equals(authAccessToken.getGrantType())){
                    newAccessTokenStr = createAccessToken(openApp
                            , authAccessToken.getGrantType(), authAccessToken.getScope(), expiresIn);
                }else {
                    //获取对应的用户信息
                    User user = platformUserMapper.selectByUserId(authAccessToken.getUserId());

                    //生成新的Access Token
                    newAccessTokenStr = createAccessToken(user, openApp
                            , authAccessToken.getGrantType(), authAccessToken.getScope(), expiresIn);
                }

                //返回数据
                result.put("token", newAccessTokenStr);
                result.put("refreshToken", req.getRefreshToken());
                result.put("expiresIn", expiresIn);
                return ResponseInfo.SUCCESS.messageToResult(result);
            }
        }catch (Exception e){
            e.printStackTrace();
            return ResponseInfo.FAILURE.messageToResult();
        }
    }


    /**
     * 保存用户授权关系
      * @param userId
     * @param clientIdStr
     * @param scopeStr
     * @return
     */
    public boolean saveAuthClientUser(String userId, String clientIdStr, String scopeStr) {
        OpenApplication openApp = openApplicationMapper.selectByClientId(clientIdStr);

        if(openApp != null){
            AuthClientUser clientUser = authClientUserMapper.selectByClientId(openApp.getAppId(), userId);
            //如果数据库中不存在记录，则插入
            if(clientUser == null){
                clientUser = new AuthClientUser();
                clientUser.setUserId(userId);
                clientUser.setAuthClientId(openApp.getAppId());
                clientUser.setAuthScope(scopeStr);
                authClientUserMapper.insert(clientUser);
            }

            return true;
        }else{
            return false;
        }
    }

    /**
     * 生成授权码
     * @param clientIdStr
     * @param scopeStr
     * @param user
     * @return
     */
    public String createAuthorizationCode(String clientIdStr, String scopeStr, User user) {
        //1. 拼装待加密字符串（clientId + scope + 当前精确到毫秒的时间戳）
        String str = clientIdStr + scopeStr + String.valueOf(DateUtils.currentTimeMillis());

        //2. SHA1加密
        String encryptedStr = EncryptUtils.sha1Hex(str);

        //3 保存本次请求所属的用户信息
        redisService.setWithExpire(encryptedStr + ":user", user, (ExpireEnum.AUTHORIZATION_CODE.getTime()), ExpireEnum.AUTHORIZATION_CODE.getTimeUnit());

        //4. 返回Authorization Code
        return encryptedStr;
    }

    /**
     * 授权码接入
     * @param user 用户信息
     * @param openApp 接入的客户端信息
     * @param grantType 授权方式
     * @param scope 允许访问的用户权限范围
     * @param expiresIn 过期时间
     * @return
     */
    
    public String createAccessToken( User user, OpenApplication openApp, String grantType, String scope, Long expiresIn) {
        Date current = new Date();
        //过期的时间戳
        Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.ACCESS_TOKEN.getTime(), null);

        //1. 拼装待加密字符串（username + clientId + 当前精确到毫秒的时间戳）
        String str = user.getAccount() + openApp.getAppId() + String.valueOf(DateUtils.currentTimeMillis());

        //2. SHA1加密  + "." + expiresIn
        String accessTokenStr = "gb." + EncryptUtils.sha1Hex(str)+ "." + expiresAt;

        //3. 保存Access Token
        AuthAccessToken savedAccessToken = authAccessTokenMapper.selectByUserIdClientIdScope(user.getUserId(),openApp.getAppId(),grantType);
        //如果存在userId + clientId + scope匹配的记录，则更新原记录，否则向数据库中插入新记录
        if(savedAccessToken != null){
            savedAccessToken.setAccessToken(accessTokenStr);
            savedAccessToken.setScope(scope);
            savedAccessToken.setExpiresIn(expiresAt);
            savedAccessToken.setUpdateUser(user.getUserId());
            savedAccessToken.setUpdateTime(current);
            authAccessTokenMapper.updateByPrimaryKeySelective(savedAccessToken);
        }else{
            savedAccessToken = new AuthAccessToken();
            savedAccessToken.setAccessToken(accessTokenStr);
            savedAccessToken.setUserId(user.getUserId());
            savedAccessToken.setMobile(user.getMobile());
            savedAccessToken.setUserName(user.getAccount());
            savedAccessToken.setClientId(openApp.getAppId());
            savedAccessToken.setExpiresIn(expiresAt);
            savedAccessToken.setScope(scope);
            savedAccessToken.setGrantType(grantType);
            savedAccessToken.setCreateUser(user.getUserId());
            savedAccessToken.setUpdateUser(user.getUserId());
            savedAccessToken.setCreateTime(current);
            savedAccessToken.setUpdateTime(current);
            authAccessTokenMapper.insertSelective(savedAccessToken);
        }
        //4. 返回Access Token
        return accessTokenStr;
    }

    /**
     * 客户端接入
     * @param openApp
     * @param grantType
     * @param scope
     * @param expiresIn
     * @return
     */
      
    public String createAccessToken(OpenApplication openApp, String grantType, String scope, Long expiresIn) {
        Date current = new Date();
        //过期的时间戳
        Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.ACCESS_TOKEN.getTime(), null);

        //1. 拼装待加密字符串（ clientId + 当前精确到毫秒的时间戳）
        String str =  openApp.getAppId() + String.valueOf(DateUtils.currentTimeMillis());

        //2. SHA1加密  + "." + expiresIn
        String accessTokenStr = "gb." + EncryptUtils.sha1Hex(str)  + "." + expiresAt;

        //3. 保存Access Token
        AuthAccessToken savedAccessToken = authAccessTokenMapper.selectByUserIdClientIdScope(openApp.getAppId(),openApp.getAppId(),grantType);
        //如果存在userId + clientId + scope匹配的记录，则更新原记录，否则向数据库中插入新记录
        if(savedAccessToken != null){
            savedAccessToken.setAccessToken(accessTokenStr);
            savedAccessToken.setExpiresIn(expiresAt);
            savedAccessToken.setScope(scope);
            savedAccessToken.setUpdateUser(openApp.getAppId());
            savedAccessToken.setUpdateTime(current);
            authAccessTokenMapper.updateByPrimaryKeySelective(savedAccessToken);
        }else{
            savedAccessToken = new AuthAccessToken();
            savedAccessToken.setAccessToken(accessTokenStr);
            savedAccessToken.setUserId(openApp.getAppId());
            savedAccessToken.setClientId(openApp.getAppId());
            savedAccessToken.setExpiresIn(expiresAt);
            savedAccessToken.setScope(scope);
            savedAccessToken.setGrantType(grantType);
            savedAccessToken.setCreateUser(openApp.getAppId());
            savedAccessToken.setUpdateUser(openApp.getAppId());
            savedAccessToken.setCreateTime(current);
            savedAccessToken.setUpdateTime(current);
            authAccessTokenMapper.insertSelective(savedAccessToken);
        }

        //4. 返回Access Token
        return accessTokenStr;
    }

    /**
     * 授权码接入
     * @param user 用户信息
     * @param authAccessToken 生成的Access Token信息
     * @return
     */
      
    public String createRefreshToken(User user, AuthAccessToken authAccessToken) {
        Date current = new Date();
        //过期时间
        Long expiresIn = DateUtils.dayToSecond(ExpireEnum.REFRESH_TOKEN.getTime());
        //过期的时间戳
        Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.REFRESH_TOKEN.getTime(), null);

        //1. 拼装待加密字符串（username + accessToken + 当前精确到毫秒的时间戳）
        String str = user.getAccount() + authAccessToken.getAccessToken() + String.valueOf(DateUtils.currentTimeMillis());

        //2. SHA1加密  + "." + expiresIn
        String refreshTokenStr = "re." + EncryptUtils.sha1Hex(str)  + "." + expiresAt;

        //3. 保存Refresh Token
        AuthRefreshToken savedRefreshToken = authRefreshTokenMapper.selectByTokenId(authAccessToken.getId());
        //如果存在tokenId匹配的记录，则更新原记录，否则向数据库中插入新记录
        if(savedRefreshToken != null){
            savedRefreshToken.setRefreshToken(refreshTokenStr);
            savedRefreshToken.setExpiresIn(expiresAt);
            savedRefreshToken.setUpdateUser(user.getUserId());
            savedRefreshToken.setUpdateTime(current);
            authRefreshTokenMapper.updateByPrimaryKeySelective(savedRefreshToken);
        }else{
            savedRefreshToken = new AuthRefreshToken();
            savedRefreshToken.setTokenId(authAccessToken.getId());
            savedRefreshToken.setRefreshToken(refreshTokenStr);
            savedRefreshToken.setExpiresIn(expiresAt);
            savedRefreshToken.setCreateUser(user.getUserId());
            savedRefreshToken.setUpdateUser(user.getUserId());
            savedRefreshToken.setCreateTime(current);
            savedRefreshToken.setUpdateTime(current);
            authRefreshTokenMapper.insertSelective(savedRefreshToken);
        }

        //4. 返回Refresh Token
        return refreshTokenStr;
    }

    /**
     * 客户端接入
     * @param authAccessToken
     * @return
     */
      
    public String createRefreshToken(AuthAccessToken authAccessToken) {
        Date current = new Date();
        //过期时间
        Long expiresIn = DateUtils.dayToSecond(ExpireEnum.REFRESH_TOKEN.getTime());
        //过期的时间戳
        Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.REFRESH_TOKEN.getTime(), null);

        //1. 拼装待加密字符串（ accessToken + 当前精确到毫秒的时间戳）
        String str = authAccessToken.getAccessToken() + String.valueOf(DateUtils.currentTimeMillis());

        //2. SHA1加密  + "." + expiresIn
        String refreshTokenStr = "re." + EncryptUtils.sha1Hex(str)  + "." + expiresAt;

        //3. 保存Refresh Token
        AuthRefreshToken savedRefreshToken = authRefreshTokenMapper.selectByTokenId(authAccessToken.getId());
        //如果存在tokenId匹配的记录，则更新原记录，否则向数据库中插入新记录
        if(savedRefreshToken != null){
            savedRefreshToken.setRefreshToken(refreshTokenStr);
            savedRefreshToken.setExpiresIn(expiresAt);
            savedRefreshToken.setUpdateTime(current);
            authRefreshTokenMapper.updateByPrimaryKeySelective(savedRefreshToken);
        }else{
            savedRefreshToken = new AuthRefreshToken();
            savedRefreshToken.setTokenId(authAccessToken.getId());
            savedRefreshToken.setRefreshToken(refreshTokenStr);
            savedRefreshToken.setExpiresIn(expiresAt);
            savedRefreshToken.setCreateTime(current);
            savedRefreshToken.setUpdateTime(current);
            authRefreshTokenMapper.insertSelective(savedRefreshToken);
        }

        //4. 返回Refresh Token
        return refreshTokenStr;
    }


}
